logo头像

学如逆水行舟

聊聊iOS中的Mach-O

聊聊iOS中的Mach-O

一 关于Mach-O

Mach-O的全称为Mach Object,是OS X与iOS上的一种可执行文件格式。Mach本身指一种操作系统的微内核标准,被用于OS X与iOS系统的内核中。相信对于移动端的iOS开发者来说,对Mach-O文件一定不陌生,我们编译打包的iOS IPA文件,内部其实就有一个可执行的Mach-O文件,我们开发的framework和.a等动态库静态库中,也会包含Mach-O文件,本篇文章,我们就来详细看看Mach-O中究竟放的是什么,Mach-O的结构是怎样的。

二 Mach-O头信息

Mach-O是一种文件格式,我们知道很多文件类型有包含有文件头,例如图片文件头中可能会包含图片的格式,编码方式等,压缩文件的文件头中包含压缩参数等等。相比,Mach-O是一种更加复杂的文件,其文件头中提供的信息也更多。我们可以在Github上找到一款开源的Mach-O文件阅读软件:MachOView。可以尝试用其打开任意一个编译好的IPA包中的Mach-O文件,例如:

可以看到,格式化后的文件内容中,有一项是Mach64 Header,这一项内容也是在Mach-O文件的最前部,这里就是Mach-O文件头。

关于Mach-O头部,我们可以在usr/include/mach-o/Loader.h文件中找到对应的定义,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 32位的Mach-O头
struct mach_header {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
};
// 64位的Mach-O头
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};

可以看到,32位的Mach-O和64位的Mach-O最大的区别只在于64位的多了一个reserved字段,此字段当前并没有特殊意义,其预留给未来使用。下面我们来详细看下这些字段的意义。

1.magic

从其数据类型uint32_t也可以看出,magic占32个二进制位。顾名思义,这个字段是一个魔数,用来区分当前Mach-O是32位的还是64位的,有两个宏对此魔数的值进行了定义:

1
2
3
4
// 32位
#define MH_MAGIC 0xfeedface
// 64位
#define MH_MAGIC_64 0xfeedfacf

你可以看下MachOView软件格式化后的此字段的值,是否和这些宏是对应的。

2.cputype

此字段占32个二进制位,用来描述CPU类型,相关宏定义在mach/machine.h头文件中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define CPU_TYPE_ANY            ((cpu_type_t) -1)
#define CPU_TYPE_VAX ((cpu_type_t) 1)
#define CPU_TYPE_MC680x0 ((cpu_type_t) 6)
// 模拟器和Maac一般为此类型
#define CPU_TYPE_X86 ((cpu_type_t) 7)
#define CPU_TYPE_I386 CPU_TYPE_X86
#define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)
#define CPU_TYPE_MC98000 ((cpu_type_t) 10)
#define CPU_TYPE_HPPA ((cpu_type_t) 11)
// iPhone真机一般为此类型
#define CPU_TYPE_ARM ((cpu_type_t) 12)
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
#define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32)
#define CPU_TYPE_MC88000 ((cpu_type_t) 13)
#define CPU_TYPE_SPARC ((cpu_type_t) 14)
#define CPU_TYPE_I860 ((cpu_type_t) 15)
#define CPU_TYPE_POWERPC ((cpu_type_t) 18)
#define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64)

其中的cpu_type_t其实就是int类型的别名。

3.cpusubtype

CPU子类型,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#define CPU_SUBTYPE_MASK        0xff000000 
#define CPU_SUBTYPE_LIB64 0x80000000
#define CPU_SUBTYPE_PTRAUTH_ABI 0x80000000
#define CPU_SUBTYPE_ANY ((cpu_subtype_t) -1)
#define CPU_SUBTYPE_MULTIPLE ((cpu_subtype_t) -1)
#define CPU_SUBTYPE_LITTLE_ENDIAN ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_BIG_ENDIAN ((cpu_subtype_t) 1)
// VAX的子类型
#define CPU_SUBTYPE_VAX_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_VAX780 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_VAX785 ((cpu_subtype_t) 2)
#define CPU_SUBTYPE_VAX750 ((cpu_subtype_t) 3)
#define CPU_SUBTYPE_VAX730 ((cpu_subtype_t) 4)
#define CPU_SUBTYPE_UVAXI ((cpu_subtype_t) 5)
#define CPU_SUBTYPE_UVAXII ((cpu_subtype_t) 6)
#define CPU_SUBTYPE_VAX8200 ((cpu_subtype_t) 7)
#define CPU_SUBTYPE_VAX8500 ((cpu_subtype_t) 8)
#define CPU_SUBTYPE_VAX8600 ((cpu_subtype_t) 9)
#define CPU_SUBTYPE_VAX8650 ((cpu_subtype_t) 10)
#define CPU_SUBTYPE_VAX8800 ((cpu_subtype_t) 11)
#define CPU_SUBTYPE_UVAXIII ((cpu_subtype_t) 12)
// MC680子类型
#define CPU_SUBTYPE_MC680x0_ALL ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_MC68030 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_MC68040 ((cpu_subtype_t) 2)
#define CPU_SUBTYPE_MC68030_ONLY ((cpu_subtype_t) 3)
// I386子类型
#define CPU_SUBTYPE_INTEL(f, m) ((cpu_subtype_t) (f) + ((m) << 4))
#define CPU_SUBTYPE_I386_ALL CPU_SUBTYPE_INTEL(3, 0)
#define CPU_SUBTYPE_386 CPU_SUBTYPE_INTEL(3, 0)
#define CPU_SUBTYPE_486 CPU_SUBTYPE_INTEL(4, 0)
#define CPU_SUBTYPE_486SX CPU_SUBTYPE_INTEL(4, 8) // 8 << 4 = 128
#define CPU_SUBTYPE_586 CPU_SUBTYPE_INTEL(5, 0)
#define CPU_SUBTYPE_PENT CPU_SUBTYPE_INTEL(5, 0)
#define CPU_SUBTYPE_PENTPRO CPU_SUBTYPE_INTEL(6, 1)
#define CPU_SUBTYPE_PENTII_M3 CPU_SUBTYPE_INTEL(6, 3)
#define CPU_SUBTYPE_PENTII_M5 CPU_SUBTYPE_INTEL(6, 5)
#define CPU_SUBTYPE_CELERON CPU_SUBTYPE_INTEL(7, 6)
#define CPU_SUBTYPE_CELERON_MOBILE CPU_SUBTYPE_INTEL(7, 7)
#define CPU_SUBTYPE_PENTIUM_3 CPU_SUBTYPE_INTEL(8, 0)
#define CPU_SUBTYPE_PENTIUM_3_M CPU_SUBTYPE_INTEL(8, 1)
#define CPU_SUBTYPE_PENTIUM_3_XEON CPU_SUBTYPE_INTEL(8, 2)
#define CPU_SUBTYPE_PENTIUM_M CPU_SUBTYPE_INTEL(9, 0)
#define CPU_SUBTYPE_PENTIUM_4 CPU_SUBTYPE_INTEL(10, 0)
#define CPU_SUBTYPE_PENTIUM_4_M CPU_SUBTYPE_INTEL(10, 1)
#define CPU_SUBTYPE_ITANIUM CPU_SUBTYPE_INTEL(11, 0)
#define CPU_SUBTYPE_ITANIUM_2 CPU_SUBTYPE_INTEL(11, 1)
#define CPU_SUBTYPE_XEON CPU_SUBTYPE_INTEL(12, 0)
#define CPU_SUBTYPE_XEON_MP CPU_SUBTYPE_INTEL(12, 1)
#define CPU_SUBTYPE_INTEL_FAMILY(x) ((x) & 15)
#define CPU_SUBTYPE_INTEL_FAMILY_MAX 15
#define CPU_SUBTYPE_INTEL_MODEL(x) ((x) >> 4)
#define CPU_SUBTYPE_INTEL_MODEL_ALL 0
// X86子类型
#define CPU_SUBTYPE_X86_ALL ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_64_ALL ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_ARCH1 ((cpu_subtype_t)4)
#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8)
#define CPU_THREADTYPE_INTEL_HTT ((cpu_threadtype_t) 1)
// MIPS子类型
#define CPU_SUBTYPE_MIPS_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_MIPS_R2300 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_MIPS_R2600 ((cpu_subtype_t) 2)
#define CPU_SUBTYPE_MIPS_R2800 ((cpu_subtype_t) 3)
#define CPU_SUBTYPE_MIPS_R2000a ((cpu_subtype_t) 4) /* pmax */
#define CPU_SUBTYPE_MIPS_R2000 ((cpu_subtype_t) 5)
#define CPU_SUBTYPE_MIPS_R3000a ((cpu_subtype_t) 6) /* 3max */
#define CPU_SUBTYPE_MIPS_R3000 ((cpu_subtype_t) 7)
// MC98000子类型
#define CPU_SUBTYPE_MC98000_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_MC98601 ((cpu_subtype_t) 1)
// HPPA子类型
#define CPU_SUBTYPE_HPPA_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_HPPA_7100 ((cpu_subtype_t) 0) /* compat */
#define CPU_SUBTYPE_HPPA_7100LC ((cpu_subtype_t) 1)
// MC88000子类型
#define CPU_SUBTYPE_MC88000_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_MC88100 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_MC88110 ((cpu_subtype_t) 2)
// SPARC子类型
#define CPU_SUBTYPE_SPARC_ALL ((cpu_subtype_t) 0)
// I860子类型
#define CPU_SUBTYPE_I860_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_I860_860 ((cpu_subtype_t) 1)
// PowerPC子类型
#define CPU_SUBTYPE_POWERPC_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_POWERPC_601 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_POWERPC_602 ((cpu_subtype_t) 2)
#define CPU_SUBTYPE_POWERPC_603 ((cpu_subtype_t) 3)
#define CPU_SUBTYPE_POWERPC_603e ((cpu_subtype_t) 4)
#define CPU_SUBTYPE_POWERPC_603ev ((cpu_subtype_t) 5)
#define CPU_SUBTYPE_POWERPC_604 ((cpu_subtype_t) 6)
#define CPU_SUBTYPE_POWERPC_604e ((cpu_subtype_t) 7)
#define CPU_SUBTYPE_POWERPC_620 ((cpu_subtype_t) 8)
#define CPU_SUBTYPE_POWERPC_750 ((cpu_subtype_t) 9)
#define CPU_SUBTYPE_POWERPC_7400 ((cpu_subtype_t) 10)
#define CPU_SUBTYPE_POWERPC_7450 ((cpu_subtype_t) 11)
#define CPU_SUBTYPE_POWERPC_970 ((cpu_subtype_t) 100)
// ARM子类型
#define CPU_SUBTYPE_ARM_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM_V4T ((cpu_subtype_t) 5)
#define CPU_SUBTYPE_ARM_V6 ((cpu_subtype_t) 6)
#define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7)
#define CPU_SUBTYPE_ARM_XSCALE ((cpu_subtype_t) 8)
#define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) /* ARMv7-A and ARMv7-R */
#define CPU_SUBTYPE_ARM_V7F ((cpu_subtype_t) 10) /* Cortex A9 */
#define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t) 11) /* Swift */
#define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t) 12)
#define CPU_SUBTYPE_ARM_V8 ((cpu_subtype_t) 13)
#define CPU_SUBTYPE_ARM_V6M ((cpu_subtype_t) 14) /* Not meant to be run under xnu */
#define CPU_SUBTYPE_ARM_V7M ((cpu_subtype_t) 15) /* Not meant to be run under xnu */
#define CPU_SUBTYPE_ARM_V7EM ((cpu_subtype_t) 16) /* Not meant to be run under xnu */
#define CPU_SUBTYPE_ARM_V8M ((cpu_subtype_t) 17) /* Not meant to be run under xnu */
// ARM64子类型
#define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM64_V8 ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_ARM64E ((cpu_subtype_t) 2)
#define CPU_SUBTYPE_ARM64_PTR_AUTH_MASK 0x0f000000
#define CPU_SUBTYPE_ARM64_PTR_AUTH_VERSION(x) (((x) & CPU_SUBTYPE_ARM64_PTR_AUTH_MASK) >> 24)
// ARM64_32子类型
#define CPU_SUBTYPE_ARM64_32_ALL ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM64_32_V8 ((cpu_subtype_t) 1)

4.filetype

此字段标记Mach-O文件的具体类型,例如当前Mach-O是可执行文件还是库文件等,此字段占32个二进制位,值定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 可重定位的目标文件,编译源码得到的中间结果
#define MH_OBJECT 0x1
// 可执行的二进制文件,iOS应用IPA包中的可执行Mach-O就是此类型的
#define MH_EXECUTE 0x2
// 修复后的VM共享库文件
#define MH_FVMLIB 0x3
// 核心转储文件
#define MH_CORE 0x4
// 预加载的可执行文件
#define MH_PRELOAD 0x5
// 动态库文件
#define MH_DYLIB 0x6
// 动态链接器文件
#define MH_DYLINKER 0x7
// 插件文件,非独立的二进制文件
#define MH_BUNDLE 0x8
// 仅用于静态链接的共享库文件
#define MH_DYLIB_STUB 0x9
// 辅助的符号文件及调试信息
#define MH_DSYM 0xa
// 内核扩展
#define MH_KEXT_BUNDLE 0xb
// 其他Mach-O组成的集合
#define MH_FILESET 0xc

5.ncmds

此字段描述需要加载的命令条数,与Mach-O文件中的Load Commands段中的数据对应。

6.sizeofcmds

与Mach-O文件中的Load Commands段中的数据对应,标记Load Commands段的长度。

7.flags

标志位字段,标记当前Mach-O文件的一些重要信息,这些flags是以二进制位或的关系定义的,也就是说可以同时拥有多个标记。定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 未带未定义的符号,没有进一步的链接依赖关系
#define MH_NOUNDEFS 0x1
// 目标文件是增量链接的输出,不能再次被链接
#define MH_INCRLINK 0x2
// 目标文件是动态链接器的输入,不能再次静态链接
#define MH_DYLDLINK 0x4
// 加载时,对象文件的未定义引用由动态链接器绑定
#define MH_BINDATLOAD 0x8
// 该文件预先绑定了其动态未定义引用
#define MH_PREBOUND 0x10
// 目标文件的只读段与可读写段进行了区分
#define MH_SPLIT_SEGS 0x20
// 延迟init
#define MH_LAZY_INIT 0x40
// 两层名称绑定
#define MH_TWOLEVEL 0x80
// 扁平名称绑定
#define MH_FORCE_FLAT 0x100
// 子image中没有多定义的符号
#define MH_NOMULTIDEFS 0x200
// 不要让dyld将此可执行文件通知预绑定代理
#define MH_NOFIXPREBINDING 0x400
// 二进制文件不是预绑定的,但可以重新进行预绑定
#define MH_PREBINDABLE 0x800
// 指示此二进制文件绑定到其依赖库的所有两级命名空间模块
#define MH_ALLMODSBOUND 0x1000
#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000
// 二进制文件已被规范化为非绑定操作
#define MH_CANONICAL 0x4000
// 二进制文件使用了弱符号
#define MH_WEAK_DEFINES 0x8000
// 最终链接的image使用了弱符号
#define MH_BINDS_TO_WEAK 0x10000
// 允许栈可执行
#define MH_ALLOW_STACK_EXECUTION 0x20000
// 当设置此位时,二进制文件声明它可以安全地用于uid为零的进程
#define MH_ROOT_SAFE 0x40000
// 当设置该位时,二进制文件声明它可以在issetugid为true的进程中安全使用
#define MH_SETUID_SAFE 0x80000
// 当在dylib上设置此位时,静态链接器不需要检查依赖的dylib,以查看是否有重新导出的依赖dylib
#define MH_NO_REEXPORTED_DYLIBS 0x100000
// 对可执行文件启用地址空间布局随机化
#define MH_PIE 0x200000
// 仅适用于dylibs。当链接到设置了此位的dylib时,如果没有从dylib引用符号,则静态链接器将自动不对dylib创建LC_LOAD_dylib加载命令
#define MH_DEAD_STRIPPABLE_DYLIB 0x400000
#define MH_HAS_TLV_DESCRIPTORS 0x800000
// 将堆标记为不可执行
#define MH_NO_HEAP_EXECUTION 0x1000000
// 扩展应用中使用
#define MH_APP_EXTENSION_SAFE 0x02000000
// nlist符号表中列出的外部符号不包括dyld信息中列出的所有符号
#define MH_NLIST_OUTOFSYNC_WITH_DYLDINFO 0x04000000
#define MH_SIM_SUPPORT 0x08000000
// 仅适用于dylibs。设置该位时,dylib是dyld共享缓存的一部分,而不是文件系统中的松散缓存。
#define MH_DYLIB_IN_CACHE 0x80000000

Mach-O文件头的内容大致就只有这些,上面我们提到过,ncmds和sizeofcmds描述了加载命令的条数和加载命令的内容大小,下面我们就来看下加载命令。

三 加载命令

在Mach-O文件中,加载命令段紧跟在Mach-O头数据后面,头数据中的字段告知了我们加载命令的条数和大小,通过这两个字段来具体的进行命令的解析和执行。

我们现在MachOView中简单浏览下这部分的数据结构,如下图:

可以看到,每条加载命令的开头结构都是一样的,即命令类型和当前命令的长度大小。其实,在loader.h头文件中有定义一个结构体来描述加载命令,如下:

1
2
3
4
struct load_command {
uint32_t cmd;
uint32_t cmdsize;
};

可以看到,每条加载命令在解析时,首先会获取到cmd和cmdsize,cmd用来标记加载命令是什么,cmdsize指明当前加载命令的字节长度,通过这两个字段,可以对命令进行完整的解析。

下面列举了常见的命令类型,对应的宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 将文件中的段(32位)映射到进程地址空间中
#define LC_SEGMENT 0x1
// 将文件中的段(64位)映射到进程地址空间中
#define LC_SEGMENT_64 0x19
// 链接编辑stab符号表信息
#define LC_SYMTAB 0x2
// 链接编辑gdb符号表信息
#define LC_SYMSEG 0x3
// 开启一个Mach线程,不开辟栈
#define LC_THREAD 0x4
// 开启一个UNIX线程
#define LC_UNIXTHREAD 0x5
// 加载指定的固定VM共享库
#define LC_LOADFVMLIB 0x6
// 修复了VM共享库标识
#define LC_IDFVMLIB 0x7
// 对象标识信息
#define LC_IDENT 0x8
// 固定VM文件包含
#define LC_FVMFILE 0x9
// prepage命令
#define LC_PREPAGE 0xa
// 动态链接编辑符号表信息
#define LC_DYSYMTAB 0xb
// 加载动态链接的共享库
#define LC_LOAD_DYLIB 0xc
// 动态链接共享库标识
#define LC_ID_DYLIB 0xd
// 加载动态链接器
#define LC_LOAD_DYLINKER 0xe
// 动态链接器标识
#define LC_ID_DYLINKER 0xf
// 动态预绑定的模块
#define LC_PREBOUND_DYLIB 0x10

// 下面的命令与链接共享库有关
// image routines
#define LC_ROUTINES 0x11 /* image routines */
#define LC_ROUTINES_64 0x1a
#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
#define LC_SUB_CLIENT 0x14 /* sub client */
#define LC_SUB_LIBRARY 0x15 /* sub library */
#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
// 加载允许丢失的动态链接共享库
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)

// 唯一的UUID,表示当前二进制文件
#define LC_UUID 0x1b
// 进行本地代码签名
#define LC_CODE_SIGNATURE 0x1d
// 本地拆分段
#define LC_SEGMENT_SPLIT_INFO 0x1e
// 加载并重新导出dylib
#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD)
// 将dylib加载延迟至首次使用
#define LC_LAZY_LOAD_DYLIB 0x20
// 加密的段信息
#define LC_ENCRYPTION_INFO 0x21
#define LC_ENCRYPTION_INFO_64 0x2C
// 压缩的dyld信息
#define LC_DYLD_INFO 0x22
// 仅限压缩的dyld信息
#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD)
// 向上加载dylib
#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD)
// 为MacOSX最小操作系统版本构建
#define LC_VERSION_MIN_MACOSX 0x24
// 为iOS最小操作系统版本构建
#define LC_VERSION_MIN_IPHONEOS 0x25
// 为TVOS最小操作系统版本构建
#define LC_VERSION_MIN_TVOS 0x2F
// 为WATCHOS最小操作系统版本构建
#define LC_VERSION_MIN_WATCHOS 0x30
// 压缩的函数起始地址表
#define LC_FUNCTION_STARTS 0x26
// dyld要像环境变量一样处理的字符串
#define LC_DYLD_ENVIRONMENT 0x27
// 同LC_UNIXTHREAD
#define LC_MAIN (0x28|LC_REQ_DYLD)
// __text段中的非说明表
#define LC_DATA_IN_CODE 0x29
// 用于生成二进制文件的源代码版本
#define LC_SOURCE_VERSION 0x2A
// 从链接的dylibs复制的代码签名DR
#define LC_DYLIB_CODE_SIGN_DRS 0x2B
// MH_OBJECT文件中的链接器选项
#define LC_LINKER_OPTION 0x2D
// MH_OBJECT文件中的优化提示
#define LC_LINKER_OPTIMIZATION_HINT 0x2E
// Mach-O文件中包含的任意数据
#define LC_NOTE 0x31
// 为平台最小操作系统版本构建
#define LC_BUILD_VERSION 0x32
// 与linkedit_data_command一起使用
#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD)
#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD)
// 与fileset_entry_command一起使用
#define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD)

在加载命令段,通过解析出每条命令的类型和大小,就可以方便的获取到命令中具体的值,准确来说,一条加载命令是由类型,大小和值三部分组成的。不同类型的命令对应的值解析出来的结构也不同,下面我们来介绍几个常见的命令。

1.LC_SEGMENT/LS_SEGMENT_64

通过我们编译的iOS空项目的Mach-O文件可以看到,其中包含了很多条LS_SEGMENT_64加载命令,此命令描述了内核应该如何设置当前应用进行的内存空间,这些段将直接从Mach-O文件中对应的位置加载到内存里。LS_SEGMENT_64命令的值的结构定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};

struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; // 段的名称
uint64_t vmaddr; // 虚拟物理地址
uint64_t vmsize; // 为此段分配的虚拟内存大小
uint64_t fileoff; // 此段在文件中的偏移量
uint64_t filesize; // 此段在文件中占用的字节数
vm_prot_t maxprot; // 段的页面所需要的最高内存保护
vm_prot_t initprot; // 段页面初始时的内存保护
uint32_t nsects; // 段中区(section)的数量
uint32_t flags; // 标记位
};

可以看到,每个段加载命令中都提供了段的基本信息,包括名称,偏移地址,字节数,以及段有包含的分区的个数,通过此命令,进程虚拟内存的设置就变得很简单,只需要根据指令来读取信息并加载到内存即可。关于段的分析我们会在后面小节做具体的介绍。

2.LC_MAIN/LS_UNIXTHREAD

当所有库加载完成后,此命令用来进行二进制程序的主线程启动。

3.LC_CODE_SIGNATURE

Mach-O包含的数字签名,如果此签名与代码不匹配,则进程会被强制关闭。LC_CODE_SIGNATURE命令的结构如下:

1
2
3
4
5
6
7
8
9
10
11
struct linkedit_data_command {
uint32_t cmd; /* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO,
LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
LC_DYLIB_CODE_SIGN_DRS,
LC_LINKER_OPTIMIZATION_HINT,
LC_DYLD_EXPORTS_TRIE, or
LC_DYLD_CHAINED_FIXUPS. */
uint32_t cmdsize; /* sizeof(struct linkedit_data_command) */
uint32_t dataoff; // 地址偏移量
uint32_t datasize; // 字节数
};

此命令的结构是一个复用的结构,很多命令都能解析成此结构,其中dataoff标记从文件的何处开始读取签名,datasize标记签名所占字节数。我们可以通过一个Mach-O文件来验证下,首先在加载命令段找到LC_CODE_SIGNATURE命令,如下:

可以看到,其offset为E8A0,size为4B00,相加后为133A0,因此我们可以得知,签名所在的位置为偏移地址为E8A0的地方,最终结束的地址为133A0,找到Code Signature段进行验证,如下:

可以看到,Code Signature段的起始偏移位置就是E8A0,最后4个字节的偏移位置为13390,再加上最后的4个字节,结尾的位置刚好为133A0,和我们计算的一致。

并非只有LC_CODE_SIGNATURE命令是采用偏移量和字节数的方式来进行加载,这种值结构的命令都是一样的逻辑,这里就不再赘述。

4.LC_SYMTAB

此命令用来加载此Mach-O文件的符号表,此命令中包含了symbol table和string table在Mach-O文件中的位置,LC_SYMTAB命令的值定义如下:

1
2
3
4
5
6
7
8
struct symtab_command {
uint32_t cmd; /* LC_SYMTAB */
uint32_t cmdsize; /* sizeof(struct symtab_command) */
uint32_t symoff; // 符号表起始位置的偏移量
uint32_t nsyms; // 符号表的字节数
uint32_t stroff; // 字符串表起始位置的偏移量
uint32_t strsize; // 字符串表的字节数
};

关于符号表和字符串表段,我们后面再详细介绍。

4.LC_DYSYMTAB

此命令用来加载动态库的符号表,对应的是Dynamic Symbol Table段的数据,此命令的值结构较复杂,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct dysymtab_command {
uint32_t cmd; /* LC_DYSYMTAB */
uint32_t cmdsize; /* sizeof(struct dysymtab_command) */

// 本地符号,用来调试
uint32_t ilocalsym; // 本地符号的位置
uint32_t nlocalsym; // 本地符号的字节数

// 定义的外部符号
uint32_t iextdefsym; // 定义的外部符号位置
uint32_t nextdefsym; // 定义的外部符号字节数

// 未定义的符号
uint32_t iundefsym; // 未定义的符号位置
uint32_t nundefsym; // 未定义的符号字节数

// 动态绑定相关
uint32_t tocoff; /* file offset to table of contents */
uint32_t ntoc; /* number of entries in table of contents */
uint32_t modtaboff; /* file offset to module table */
uint32_t nmodtab; /* number of module table entries */
uint32_t extrefsymoff; /* offset to referenced symbol table */
uint32_t nextrefsyms; /* number of referenced symbol table entries */

// 间接符号表
uint32_t indirectsymoff; /* file offset to the indirect symbol table */
uint32_t nindirectsyms; /* number of indirect symbol table entries */

// 重定位相关
uint32_t extreloff; /* offset to external relocation entries */
uint32_t nextrel; /* number of external relocation entries */

// 本地条目相关
uint32_t locreloff; /* offset to local relocation entries */
uint32_t nlocrel; /* number of local relocation entries */

};

5.LC_LOAD_DYLINKER

此命令告知内核需要调用的动态链接器的位置,值结构定义如下:

1
2
3
4
5
6
struct dylinker_command {
uint32_t cmd; /* LC_ID_DYLINKER, LC_LOAD_DYLINKER or
LC_DYLD_ENVIRONMENT */
uint32_t cmdsize; /* includes pathname string */
union lc_str name; // 包含链接器的路径名称字符串起始位置的偏移,真正的动态链接器一般为/usr/lib/dyld
};

6.LC_UUID

此命令加载时可以读取到一个128位的UUID值,结构如下:

1
2
3
4
5
struct uuid_command {
uint32_t cmd; /* LC_UUID */
uint32_t cmdsize; /* sizeof(struct uuid_command) */
uint8_t uuid[16]; /* the 128-bit uuid */
};

7.LC_LOAD_DYLIB

此命令用来加载额外的动态库,这也是一个非常重要的命令,最简单的iOS应用也离不开动态库的使用,通过真实的Mach-O文件我们也可以看到,通常会有多条LC_LOAD_DYLIB命令,每条命令指定一个要加载的动态库。此命令的值结构定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct dylib_command {
uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
LC_REEXPORT_DYLIB */
uint32_t cmdsize; /* includes pathname string */
struct dylib dylib; // 动态库结构体
};

struct dylib {
union lc_str name; // 动态库所在路径字符串的起始位置(相对与当前命令起始的偏移字节)
uint32_t timestamp; // 库编译时的时间戳
uint32_t current_version; // 动态库版本号
uint32_t compatibility_version; // 兼容版本号
};

以UIKit为例,一般的iOS应用都会使用到这个动态库,此命令如下:

8.LC_RPATH

此命令用来进行@rpath外部路径变量的映射,我们知道,除了系统本身提供了一些动态共享库外,我们也可以使用外部自定义的动态库,这类外部动态库在加载的时候也需要一个明确的路径,通常在加载外部动态库的命令中,库的路径是以@rpath开头的,Xcode的编译选项中可以自定义设置外部库的寻找路径,同时使用此命令进行解析。此命令的值结构如下:

1
2
3
4
5
struct rpath_command {
uint32_t cmd; /* LC_RPATH */
uint32_t cmdsize; /* includes string */
union lc_str path;
};

关于动态库这部分的内容,可以参考之前的一篇文章,其中有更详细的介绍。

https://my.oschina.net/u/2340880/blog/5323143

9.LC_FUNCTION_STARTS

此命令的结构与LC_CODE_SIGNATURE一致,用来描述函数表段所在的起始位置和字节数。关于函数表,后面会具体介绍。

四 段与分区

前面,我们将常见的加载命令进行了介绍,也有提到LS_SEGMENT是非常重要的一个命令,其指导着内核如果将Mach-O文件中的各个段数据加载到内存里。此命令的结构中有一个名为segname的字段,其表示要加载的段的名字,常用宏定义如下:

1
2
3
4
5
6
7
8
#define	SEG_PAGEZERO	"__PAGEZERO"  // 空指针异常捕获段
#define SEG_TEXT "__TEXT" // 代码段
#define SEG_DATA "__DATA" // 数据段
#define SEG_OBJC "__OBJC" // OC运行时段
#define SEG_ICON "__ICON" // ICON段
#define SEG_LINKEDIT "__LINKEDIT" // 链接器使用的符号和其他表段
#define SEG_UNIXSTACK "__UNIXSTACK" // unix栈段
#define SEG_IMPORT "__IMPORT" // dyld段

其中,__PAGEZERO段在进入虚拟内存时是不会分配真实的物理地址的,其只是告诉开发者此段虚拟内存不能使用,最大的作用是如果产生了空指针,会访问到此段中的地址,会直接被捕获报错。

__TEXT段为代码段,其中又包含多个区,命令中的nsects字段描述了当前段有多少个区,区的信息也在命令中进行读取,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct section_64 { /* for 64-bit architectures */
char sectname[16]; // 分区名
char segname[16]; // 段名
uint64_t addr; // 分区地址
uint64_t size; // 字节数
uint32_t offset; // 分区在文件中的偏移量
uint32_t align; // 对齐方式
uint32_t reloff; // 重定位条目在文件中的偏移量
uint32_t nreloc; // 重定位条目数
uint32_t flags; // 分区标记
uint32_t reserved1; // 预留
uint32_t reserved2; // 预留
uint32_t reserved3; // 预留
};

通过这些信息可以在Mach-O文件中找到具体的分区所在的位置,将其加载进内存。

__TEXT段下,常见的区列举如下:

__text:主程序代码区

__stubs:动态链接桩区

__objc_methname:Objective-C方法名区

__objc_classname:Objective-C类名区

__objc_methtype:Objective-C方法类型区

__cstring:硬编码的C语言字符串区

__entitlements:配置文件区

__DATA_CONST段下,常见的分区列举如下:

__got:Non-Lazy Symbol Pointers区

__cfstring:程序中使用的CFStringRef区

__objc_classlist:Objective-C类列表区

__objc_protolist:Objective-C原型列表区

__objc_imginfo:Objective-C镜像信息区

__DATA段下,常见的分区列举如下:

__objc_const:Objective-C常量区

__objc_selrefs:Objective-C自引用区

__objc_classrefs:Objective-C类引用区

__objc_supperrefs:Objective-C超类引用区

五 Mach-O的”加载“到底加载的是什么

现在,我们对Mach-O文件的整体架构已经有了清晰的了解,如果将一个Mach-O文件比作一个完成的工程产品,则文件头好比是此产品的信息表,记录了产品的一些基础信息。加载命令则是产品的组装说明书,用来具体指导此工程产品的组装,之后的内容就是一个个独立的工程组件,我们只需要按照说明书的描述进行组装即可。下面我们对几个核心的区进行介绍。

1.代码段代码区

首先,对于一个iOS应用的Mach-O文件来说,__TEXT段__text分区是必不可少的,其便是程序的核心代码区,通过可视化的工具可以看到,其中数据就是汇编指令,如下:

2.代码段动态链接桩区

__TEXT段__text区中的代码在调用的动态链接函数需要进行绑定,__TEXT段的__stubs区便是留的动态链接的桩,其具体的解析在Dynamic Symbol Table动态符号表段中。举个例子,__stubs区的符号解析结果如下:

可以看到,可视化工具中,这些桩已经被解析成了具体的符号,那么是如何解析的呢。其实也很简单,首先每6个字节对应一个符号,之所以是6个字节,是LC_SEGMENT命令中的Size of Stubs字段约定的,如下:

具体的符号定义在Dynamic Symbol Table里面,从前往后与__stubs中的桩一一对应,可以观察下Dynamic Symbol Table表中的数据,如下:

可视化工具已经帮我们将符号进行了解析,实际上动态符号表中存放的只是符号所在位置的下标,并非是符号本身,所有符号都记录在Symbol Table中,以动态符号表中的第一个符号为例,其存储的值为0xB7,转成10进制为183,表示符号表中的第183个符号,找到Symbol Table中下标为183的符号,如下:

事实上,Symbol Table中记录的是一种标准化的符号结构,其并不会存具体的符号字符串,所有字符串都存在字符串表中,以便节省内存。可以看到,Symbol Table中第183个符号记录其在字符串表中的位置为0xCE,我们在找到String Table,其起始位置为0xD0A0,加上这里的偏移量0xCE为0xD16E,说明要解析的符号存放在0xD16E的位置,String Table中记录的符号是以0x00为分割的,此处的后一个符号刚好就是_NSStringFromClass,如下:

到此,动态链接桩被解析完成。其实除了__TEXT段的__stubs区,__DATA_CONST段的__got区也是通过类似的方式来绑定符号的,只是__got区的符号需要链接时绑定,而__stubs区的符号允许运行时绑定,这里后面就不再赘述。

现在,让我们总结上符号绑定的完整过程。首先代码区中的符号需要到__stubs区或__got区进行绑定,__stubs区和__got区对应的加载命令记录了这两个去对应的符号解析的字节数以及在Dynamic Symbol Table表中的起始位置,Dynamic Symbol Table表中记录了当前符号在Symbol Table表中的下标,Symbol表记录了具体的符号实体,其中会包含当前符号在String Table中的位置,符号类型等信息,从String Table中找到具体的符号字符串。

3.Objective-C方法名区,Objective-C类名区,Objective-C方法类型区,C语言字符串区

__TEXT段的的__objc_methname区顾名思义,记录了应用中所有的Objective-C方法名,直接以字符串的方式存储。

__TEXT段的的__objc_classname区顾名思义,记录了应用中(非动态库)所有的Objective-C类名,直接以字符串的方式存储。

__TEXT段的的__objc_methtype区顾名思义,记录了应用中所有的Objective-C方法的类型,直接以字符串的方式存储。

__TEXT段的的__cstring区顾名思义,记录了应用中所有的C语言字符串,直接以字符串的方式存储。

六 通过otool工具解析Mach-O

整体看来,Mach-O文件的结构还是比较复杂的,如果没有可视化的工具,要解析此文件还是有些困难。除了MachOView工具外,我们还可以使用原生的otool命令来获取Mach-O文件中的而一些内容。文章的最后,我们来介绍几个常用的命令。

1.查看Mach-O文件头

命令:otool -h MachOTest.app/MachOTest 或 otool -hV MachOTest.app/MachOTest

hV参数的命令会自动进行可视化。

示例结果:

2.查看Mach-O的加载命令

命令:otool -lV MachOTest.app/MachOTest

示例结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
MachOTest.app/MachOTest:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot ---
initprot ---
nsects 0
flags (none)
Load command 1
cmd LC_SEGMENT_64
cmdsize 712
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000004000
fileoff 0
filesize 16384
maxprot r-x
initprot r-x
nsects 8
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000100001fb0
size 0x000000000000051d
offset 8112
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
Section
sectname __stubs
segname __TEXT
addr 0x00000001000024ce
size 0x000000000000003c
offset 9422
align 2^1 (2)
reloff 0
nreloc 0
type S_SYMBOL_STUBS
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0 (index into indirect symbol table)
reserved2 6 (size of stubs)
Section
sectname __objc_methname
segname __TEXT
addr 0x000000010000250a
size 0x0000000000000dec
offset 9482
align 2^0 (1)
reloff 0
nreloc 0
type S_CSTRING_LITERALS
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_classname
segname __TEXT
addr 0x00000001000032f6
size 0x0000000000000070
offset 13046
align 2^0 (1)
reloff 0
nreloc 0
type S_CSTRING_LITERALS
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_methtype
segname __TEXT
addr 0x0000000100003366
size 0x0000000000000b29
offset 13158
align 2^0 (1)
reloff 0
nreloc 0
type S_CSTRING_LITERALS
attributes (none)
reserved1 0
reserved2 0
Section
sectname __cstring
segname __TEXT
addr 0x0000000100003e8f
size 0x0000000000000016
offset 16015
align 2^0 (1)
reloff 0
nreloc 0
type S_CSTRING_LITERALS
attributes (none)
reserved1 0
reserved2 0
Section
sectname __entitlements
segname __TEXT
addr 0x0000000100003ea5
size 0x000000000000010e
offset 16037
align 2^0 (1)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __unwind_info
segname __TEXT
addr 0x0000000100003fb4
size 0x0000000000000048
offset 16308
align 2^2 (4)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Load command 2
cmd LC_SEGMENT_64
cmdsize 472
segname __DATA_CONST
vmaddr 0x0000000100004000
vmsize 0x0000000000004000
fileoff 16384
filesize 16384
maxprot rw-
initprot rw-
nsects 5
flags SG_READ_ONLY
Section
sectname __got
segname __DATA_CONST
addr 0x0000000100004000
size 0x0000000000000060
offset 16384
align 2^3 (8)
reloff 0
nreloc 0
type S_NON_LAZY_SYMBOL_POINTERS
attributes (none)
reserved1 10 (index into indirect symbol table)
reserved2 0
Section
sectname __cfstring
segname __DATA_CONST
addr 0x0000000100004060
size 0x0000000000000020
offset 16480
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_classlist
segname __DATA_CONST
addr 0x0000000100004080
size 0x0000000000000018
offset 16512
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes NO_DEAD_STRIP
reserved1 0
reserved2 0
Section
sectname __objc_protolist
segname __DATA_CONST
addr 0x0000000100004098
size 0x0000000000000020
offset 16536
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_imageinfo
segname __DATA_CONST
addr 0x00000001000040b8
size 0x0000000000000008
offset 16568
align 2^2 (4)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Load command 3
cmd LC_SEGMENT_64
cmdsize 632
segname __DATA
vmaddr 0x0000000100008000
vmsize 0x0000000000004000
fileoff 32768
filesize 16384
maxprot rw-
initprot rw-
nsects 7
flags (none)
Section
sectname __objc_const
segname __DATA
addr 0x0000000100008000
size 0x0000000000001368
offset 32768
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_selrefs
segname __DATA
addr 0x0000000100009368
size 0x0000000000000018
offset 37736
align 2^3 (8)
reloff 0
nreloc 0
type S_LITERAL_POINTERS
attributes NO_DEAD_STRIP
reserved1 0
reserved2 0
Section
sectname __objc_classrefs
segname __DATA
addr 0x0000000100009380
size 0x0000000000000010
offset 37760
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes NO_DEAD_STRIP
reserved1 0
reserved2 0
Section
sectname __objc_superrefs
segname __DATA
addr 0x0000000100009390
size 0x0000000000000008
offset 37776
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes NO_DEAD_STRIP
reserved1 0
reserved2 0
Section
sectname __objc_ivar
segname __DATA
addr 0x0000000100009398
size 0x0000000000000008
offset 37784
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __objc_data
segname __DATA
addr 0x00000001000093a0
size 0x00000000000000f0
offset 37792
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Section
sectname __data
segname __DATA
addr 0x0000000100009490
size 0x0000000000000180
offset 38032
align 2^3 (8)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
Load command 4
cmd LC_SEGMENT_64
cmdsize 72
segname __LINKEDIT
vmaddr 0x000000010000c000
vmsize 0x0000000000008000
fileoff 49152
filesize 29600
maxprot r--
initprot r--
nsects 0
flags (none)
Load command 5
cmd LC_DYLD_CHAINED_FIXUPS
cmdsize 16
dataoff 49152
datasize 680
Load command 6
cmd LC_DYLD_EXPORTS_TRIE
cmdsize 16
dataoff 49832
datasize 216
Load command 7
cmd LC_SYMTAB
cmdsize 24
symoff 50072
nsyms 203
stroff 53408
strsize 6144
Load command 8
cmd LC_DYSYMTAB
cmdsize 80
ilocalsym 0
nlocalsym 175
iextdefsym 175
nextdefsym 8
iundefsym 183
nundefsym 20
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 53320
nindirectsyms 22
extreloff 0
nextrel 0
locreloff 0
nlocrel 0
Load command 9
cmd LC_LOAD_DYLINKER
cmdsize 32
name /usr/lib/dyld (offset 12)
Load command 10
cmd LC_UUID
cmdsize 24
uuid 05EC375B-F3C2-3C5A-99D2-28FB938FF144
Load command 11
cmd LC_BUILD_VERSION
cmdsize 32
platform IOSSIMULATOR
minos 15.5
sdk 15.5
ntools 1
tool LD
version 764.0
Load command 12
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load command 13
cmd LC_MAIN
cmdsize 24
entryoff 8672
stacksize 0
Load command 14
cmd LC_LOAD_DYLIB
cmdsize 88
name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1860.0.0
compatibility version 300.0.0
Load command 15
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libobjc.A.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 228.0.0
compatibility version 1.0.0
Load command 16
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1311.120.1
compatibility version 1.0.0
Load command 17
cmd LC_LOAD_DYLIB
cmdsize 96
name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1860.0.0
compatibility version 150.0.0
Load command 18
cmd LC_LOAD_DYLIB
cmdsize 80
name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 5610.0.0
compatibility version 1.0.0
Load command 19
cmd LC_RPATH
cmdsize 40
path @executable_path/Frameworks (offset 12)
Load command 20
cmd LC_FUNCTION_STARTS
cmdsize 16
dataoff 50048
datasize 24
Load command 21
cmd LC_DATA_IN_CODE
cmdsize 16
dataoff 50072
datasize 0
Load command 22
cmd LC_CODE_SIGNATURE
cmdsize 16
dataoff 59552
datasize 19200

3.查看依赖的动态库

命令:otool -L MachOTest.app/MachOTest

示例结果:

1
2
3
4
5
6
MachOTest.app/MachOTest:
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1860.0.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.120.1)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1860.0.0)
/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 5610.0.0)

专注技术,懂的热爱,愿意分享,做个朋友

QQ:316045346